#!/bin/bash

# Copyright 2016 The Fuchsia Authors
#
# Use of this source code is governed by a MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT

# This script will perform static analyses provided by Clang Static analyzers
# on Zircon. It requires either a prebuilt Clang toolchan or a Clang toolchain
# built from official Clang repository. For instructions on how to obtain a
# prebuilt toolchain or build the toolchain from scratch, please refer to
# document at
# https://fuchsia.googlesource.com/zircon/+/master/docs/getting_started.md

set -eu

ORIGINAL_CWD="$(pwd)"

# These are the default checkers for clang and are always on by defualt,
# unless they are explicitley disabled
CHECKERS_DEFAULT_ON="\
  apiModeling.google.GTest \
  core.CallAndMessage \
  core.DynamicTypePropagation \
  core.DivideZero \
  core.NonNullParamChecker \
  core.NullDereference \
  core.StackAddressEscape \
  core.UndefinedBinaryOperatorResult \
  core.VLASize \
  core.uninitialized.ArraySubscript \
  core.uninitialized.Assign \
  core.uninitialized.Branch \
  core.uninitialized.CapturedBlockVariable \
  core.uninitialized.UndefReturn \
  cplusplus.NewDelete \
  cplusplus.NewDeleteLeaks \
  cplusplus.SelfAssignment \
  deadcode.DeadStores \
  nullability.NullPassedToNonnull \
  nullability.NullReturnedFromNonnull \
  security.insecureAPI.UncheckedReturn \
  security.insecureAPI.getpw \
  security.insecureAPI.gets \
  security.insecureAPI.mkstemp \
  security.insecureAPI.mktemp \
  security.insecureAPI.vfork \
  unix.API \
  unix.Malloc \
  unix.MallocSizeof \
  unix.MismatchedDeallocator \
  unix.Vfork \
  unix.cstring.BadSizeArg \
  unix.cstring.NullArg \
"

# Zircon specific checkers
# Content may change in the future
# TODO: This checker will only work after https://reviews.llvm.org/D36024 lands.
CHECKERS_ZIRCON="alpha.zircon.ZirconHandleChecker"

# Checkers that should be enabled
CHECKERS_TO_ENABLE=""

# Checkers that should be disabled
CHECKERS_TO_DISABLE=""

# Checkers Args that should be passed to Clang Static Analyzer
CHECKERS=""

func_disable_checkers() {
  # Disable default checkers
  for i in $CHECKERS_TO_DISABLE; do
    CHECKERS="$CHECKERS -disable-checker $i"
  done
}

func_enable_checkers() {
  # Enable the checkers stored in $CHECKERS_TO_ENABLE
  for i in $CHECKERS_TO_ENABLE; do
    CHECKERS="$CHECKERS -enable-checker $i"
  done
}

func_test_exist() {
  if [ ! -e "$1" ]; then
    echo "$1 does not exist! Please check your input. Aborting!"
    exit -1
  fi
}

func_trap_handler() {
  cd "$ORIGINAL_CWD"
  exit -1
}

# Register trap
trap func_trap_handler SIGHUP SIGINT SIGTERM EXIT

# Analyzer run mode. clang|zircon|all
RUN_MODE="clang"
# Path to python version of scan_build
SCAN_BUILD_PY=""
# Path to CC wrapper of scan_build_py
SCAN_BUILD_PY_CC=""
# Path to CXX wrapper of scan_build_py
SCAN_BUILD_PY_CXX=""
# Build target
BUILD_TARGET=""

# Path to clang
CLANG_PATH=""

# Path to clang++
CLANGXX_PATH=""

# Path to toolchain
TOOLCHAIN_PREFIX=""

# Path to directory that contains scan-build-py/bin
SCAN_BUILD_PY_PREFIX=""

# Do not process unit tests flag
DISABLE_UTEST=false

function func_help {
  echo "help:"
  echo "-p <toolchain prefix>     : path to the directory containing bin/clang"
  echo "-s <scan-build-py prefix> : path to the directory bin/scan-build"
  echo "-o <output dir>           : path to output dir (default: AnalysisResult)"
  echo "-m <clang|zircon|all>    : run mode. clang mode will only enable checkers that"
  echo "                            is enabled by default. zircon mode will only enable"
  echo "                            zircon related checkers. all mode will enable both"
  echo "                            default checkers and zircon related checkers."
  echo "                            Default value is clang."
  echo "-t <target>               : build target. Default is zircon-pc-x86-64"
  echo "-n                        : do not run analyzer on unit tests"
  echo "-h                        : for help"
  exit 1
}

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  # if $SOURCE was a relative symlink, we need to resolve it relative to the
  # path where the symlink file was located
  [[ "$SOURCE" != /* ]] && SOURCE="$SCRIPT_DIR/$SOURCE"
done
SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
ZIRCON_ROOT="$SCRIPT_DIR/.."

# Path to output analysis results
OUT_DIR="$ZIRCON_ROOT/AnalysisResult"

# Read args from command line
while getopts "p:s:t:o:m:hn" opt; do
  case $opt in
    p) TOOLCHAIN_PREFIX="$OPTARG";;
    s) SCAN_BUILD_PY_PREFIX="$OPTARG";;
    t) BUILD_TARGET="$OPTARG";;
    o) OUT_DIR="$OPTARG";;
    m) RUN_MODE="${OPTARG,,}";;
    h) func_help;;
    n) DISABLE_UTEST=true;;
    \?)
      echo "Inavlid option"
      func_help
  esac
done

# Determine the clang prefix
if [ -z "$TOOLCHAIN_PREFIX" ]; then
  # User did not provide toolchain prefix
  # Assume user prefer prebuilt toolchain
  PREBUILT_DIR="$ZIRCON_ROOT/prebuilt/downloads"
  # Determine OS type
  OS_STR="$(uname)"
  if [ "$OS_STR" = "Darwin" ]; then
    PREBUILT_DIR="$PREBUILT_DIR/clang+llvm-x86_64-darwin"
  elif [ "$OS_STR" = "Linux" ]; then
    PREBUILT_DIR="$PREBUILT_DIR/clang+llvm-x86_64-linux"
  fi
  if [ ! -d "$PREBUILT_DIR" ]; then
    echo "Toolchain prefix is not defined and prebuilt toolchain has not yet been downloaded."
    echo "Abort!"
    exit -1
  fi
  TOOLCHAIN_PREFIX="$PREBUILT_DIR"
fi

CLANG_PATH="$TOOLCHAIN_PREFIX/bin/clang"
CLANGXX_PATH="$TOOLCHAIN_PREFIX/bin/clang++"

# Check if clang exists
func_test_exist "$CLANG_PATH"
func_test_exist "$CLANGXX_PATH"

# Looking for scan-build-py
# Prebuild does not have scan-build-py
if [ -z "$SCAN_BUILD_PY_PREFIX" ]; then
  # SCAN_BUILD_PY_PREFIX not defined
  # Try fuchsia/third_party
  SCAN_BUILD_PY_PREFIX="$ZIRCON_ROOT/../third_party/llvm/tools/clang/tools/scan-build-py"
fi

if [ ! -d "$SCAN_BUILD_PY_PREFIX" ]; then
  echo "scan-build-py is not found at $SCAN_BUILD_PY_PREFIX, Aborting!"
  exit -1
fi

SCAN_BUILD_PY="$SCAN_BUILD_PY_PREFIX/bin/scan-build"
SCAN_BUILD_PY_CC="$SCAN_BUILD_PY_PREFIX/bin/analyze-cc"
SCAN_BUILD_PY_CXX="$SCAN_BUILD_PY_PREFIX/bin/analyze-c++"

# Test if scan-build exists
func_test_exist "$SCAN_BUILD_PY"
func_test_exist "$SCAN_BUILD_PY_CC"
func_test_exist "$SCAN_BUILD_PY_CXX"

# Construct Checker Args that should be passed to scan-build
if [ "$RUN_MODE" = "zircon" ]; then
  CHECKERS_TO_DISABLE="${CHECKERS_TO_DISABLE} ${CHECKERS_DEFAULT_ON}"
  CHECKERS_TO_ENABLE="${CHECKERS_TO_ENABLE} ${CHECKERS_ZIRCON}"
elif [ "$RUN_MODE" = "all" ]; then
  CHECKERS_TO_ENABLE="${CHECKERS_TO_ENABLE} ${CHECKERS_ZIRCON}"
fi
func_disable_checkers
func_enable_checkers

# All clear, perform analysis
# Change dir to zircon
cd "$ZIRCON_ROOT"
# Run scan-build on make
if [ ! -z "${BUILD_TARGET}" ]; then
  make USE_CLANG=true "${BUILD_TARGET}" clean &> /dev/null
else
  make USE_CLANG=true clean &> /dev/null
fi

CMDL="${SCAN_BUILD_PY}"
CMDL="${CMDL} --use-cc ${CLANG_PATH}"
CMDL="${CMDL} --use-c++ ${CLANGXX_PATH}"
CMDL="${CMDL} --use-analyzer ${CLANGXX_PATH}"
CMDL="${CMDL} -o ${OUT_DIR}"
CMDL="${CMDL} ${CHECKERS}"
CMDL="${CMDL} make USE_CLANG=true"
if [ "${DISABLE_UTEST}" = true ]; then
  CMDL="$CMDL DISABLE_UTEST=true"
fi
CMDL="$CMDL CC=${SCAN_BUILD_PY_CC}"
CMDL="$CMDL CXX=${SCAN_BUILD_PY_CXX}"
CMDL="$CMDL -j32"
if [ ! -z "${BUILD_TARGET}" ]; then
  CMDL="$CMDL ${BUILD_TARGET}"
fi

# Execute
$CMDL

exit 0;
